#ifndef     __GPIO_H
#define     __GPIO_H

#include "./compat.h"

namespace device::gpio {


enum class Mode : uint8_t {
    PP_OUT = 0, // 推挽输出
    OD_OUT = 1, // 开漏输出
    AF_PP_OUT = 2, // 复用推挽输出
    AF_OD_OUT = 3, // 复用开漏输出

    FLOAT_INPUT = 4, // 输入模式
    INPUT = 5, // 上拉或者下拉输入
    ANALOG_INPUT = 6, // 模拟输入
};

enum class Pupd : uint8_t {
    UP = 0,
    DOWN = 1,
};

#define PORT(X)     reinterpret_cast<GPIO_TypeDef *>(X)

namespace mode {

template <Mode>
inline constexpr uint8_t value = 0; // 默认模拟输出

template <>
inline constexpr uint8_t value<Mode::PP_OUT> = 0x03;

template <>
inline constexpr uint8_t value<Mode::OD_OUT> = 0x07;

template <>
inline constexpr uint8_t value<Mode::AF_PP_OUT> = 0x0B;

template <>
inline constexpr uint8_t value<Mode::AF_OD_OUT> = 0x0F;

template <>
inline constexpr uint8_t value<Mode::FLOAT_INPUT> = 0x04;

template <>
inline constexpr uint8_t value<Mode::INPUT> = 0x08;

}

template <uint32_t Base, uint8_t Pin>
struct Gpio {

    template <Mode m>
    static inline void set_mode() {
        static_assert(Pin < 16, "pin must < 16");
        constexpr uint8_t val = mode::value<m>;

        if constexpr (Pin < 8) {
            constexpr uint32_t offset = 4 * Pin;
			constexpr uint32_t mask = 0x0F << offset;
            PORT(Base)->CRL = (PORT(Base)->CRL & (~mask)) | (val << offset);
        } else {
            constexpr uint32_t offset = 4 * (Pin - 8);
			constexpr uint32_t mask = 0x0F << offset;
            PORT(Base)->CRH = (PORT(Base)->CRH & (~mask)) | (val << offset);
        }
    }

    static inline void set() {
        PORT(Base)->BSRR = 0x01 << Pin;
    }

    static inline void clr() {
        PORT(Base)->BRR = 0x01 << Pin;
    }

    static inline void toggle() {
        PORT(Base)->ODR ^= 0x01 << Pin;
    }

    static inline bool read() {
        return (PORT(Base)->IDR & (0x01 << Pin)) != 0;
    }

    template <Pupd pupd>
    static inline void set_pupd() {
        static_assert(Pin < 16, "pin must < 16");
        if constexpr (pupd == Pupd::UP) {
            set();
        } else {
            clr();
        }
    }
};


template <Mode mode, typename... Ts>
inline void config_pins() {
    ((Ts::template set_mode<mode>()),...);
}

template <typename... Ts>
inline void config_pp_out() {
    config_pins<Mode::PP_OUT, Ts...>();
    (Ts::clr(), ...);
}

template <typename... Ts>
inline void config_od_out() {
    config_pins<Mode::OD_OUT, Ts...>();
    (Ts::set(), ...);
}

template <typename... Ts>
inline void config_af_pp_out() {
    config_pins<Mode::AF_PP_OUT, Ts...>();
}

template <typename... Ts>
inline void config_af_od_out() {
    config_pins<Mode::AF_OD_OUT, Ts...>();
}

template <typename... Ts>
inline void config_float_input() {
    config_pins<Mode::FLOAT_INPUT, Ts...>();
}

template <typename... Ts>
inline void config_up_input() {
    config_pins<Mode::INPUT, Ts...>();
    (Ts::set(), ...);
}

template <typename... Ts>
inline void config_down_input() {
    config_pins<Mode::INPUT, Ts...>();
    (Ts::clr(), ...);
}

template <typename... Ts>
inline void config_analog_input() {
    config_pins<Mode::ANALOG_INPUT, Ts...>();
}

template <uint8_t Pin>
using PA = Gpio<GPIOA_BASE, Pin>;

template <uint8_t Pin>
using PB = Gpio<GPIOB_BASE, Pin>;

template <uint8_t Pin>
using PC = Gpio<GPIOC_BASE, Pin>;

template <uint8_t Pin>
using PD = Gpio<GPIOD_BASE, Pin>;

template <uint8_t Pin>
using PE = Gpio<GPIOE_BASE, Pin>;

template <uint8_t Pin>
using PF = Gpio<GPIOF_BASE, Pin>;

template <uint8_t Pin>
using PG = Gpio<GPIOG_BASE, Pin>;

}


#endif
